/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "tls_certificate.h"

#include <map>

#include <openssl/asn1.h>
#include <openssl/bn.h>
#include <openssl/pem.h>
#include <openssl/x509.h>

#include "netstack_log.h"

namespace OHOS {
namespace NetStack {

namespace {
constexpr const char *BIO_FILE_FLAG = "r";
constexpr const char *FILE_OPEN_FLAG = "rb";
constexpr const char *CERT_VERSION_FLAG = "V";
constexpr int FILE_READ_CERT_LEN = 4096;
} // namespace

TLSCertificate::TLSCertificate(const TLSCertificate &certificate) = default;

TLSCertificate::TLSCertificate(const std::string &data, EncodingFormat format, CertType certType)
{
    if (data.empty()) {
        NETSTACK_LOGE("TlsCertificate::TlsCertificate data is empty");
        return;
    }
    switch (format) {
        case PEM:
            if (!CertificateFromPem(data, certType)) {
                NETSTACK_LOGE("return CertificateFromPem(data, certs) is false");
            }
            break;
        case DER:
            if (!CertificateFromDer(data, certType)) {
                NETSTACK_LOGE("return CertificateFromDer(data, certs) is false");
            }
            break;
        default:
            NETSTACK_LOGE("%{public}u is not supported, only support PEM =0 and DER = 1", format);
    }
}

TLSCertificate::TLSCertificate(const std::string &data, CertType certType)
{
    if (data.empty()) {
        NETSTACK_LOGE("TlsCertificate::TlsCertificate(const std::string &data, CertType certType) data is empty");
        return;
    }
    if (!CertificateFromData(data, certType)) {
        NETSTACK_LOGE("return CertificatesFromPem(data, certs) is false");
    }
}

TLSCertificate &TLSCertificate::operator= (const TLSCertificate &other)
{
    if (other.x509_ != nullptr) {
        x509_ = X509_new();
        x509_ = other.x509_;
    }
    version_ = other.version_;
    serialNumber_ = other.serialNumber_;
    notValidBefore_ = other.notValidBefore_;
    notValidAfter_ = other.notValidBefore_;
    signatureAlgorithm_ = other.signatureAlgorithm_;
    localCertString_ = other.localCertString_;
    caCertString_ = other.caCertString_;
    return *this;
}

bool TLSCertificate::CertificateFromData(const std::string &data, CertType certType)
{
    if (data.empty()) {
        NETSTACK_LOGE("TlsCertificate::CertificateFromData: data is empty");
        return false;
    }
    BIO *bio = BIO_new_mem_buf(data.c_str(), -1);
    if (!bio) {
        NETSTACK_LOGE("TlsCertificate(const std::string &data, CertType certType) ---");
        return false;
    }
    X509 *x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
    BIO_free(bio);

    if (!x509) {
        NETSTACK_LOGE("--- TlsCertificate::CertificateFromData: x509 is null ---");
        return false;
    }

    x509_ = X509_dup(x509);
    if (!AnalysisCertificate(certType, x509)) {
        NETSTACK_LOGE("TlsCertificate::CertificateFromData: AnalysisCertificate is false");
        X509_free(x509);
        return false;
    }
    X509_free(x509);
    return true;
}

bool TLSCertificate::CertificateFromPem(const std::string &data, CertType certType)
{
    BIO *bio = BIO_new_file((data.c_str()), BIO_FILE_FLAG);
    if (!bio) {
        NETSTACK_LOGE("bio new file fail errno");
        return false;
    }
    X509 *x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
    BIO_free(bio);

    if (!x509) {
        NETSTACK_LOGE("--- TlsCertificate::CertificateFromPem: x509 is null ---");
        return false;
    }

    x509_ = X509_dup(x509);
    if (!AnalysisCertificate(certType, x509)) {
        NETSTACK_LOGE("TlsCertificate::CertificateFromPem: AnalysisCertificate is false");
        X509_free(x509);
        return false;
    }
    X509_free(x509);
    return true;
}

bool TLSCertificate::CertificateFromDer(const std::string &data, CertType certType)
{
    FILE *fp = nullptr;
    fp = fopen(data.c_str(), FILE_OPEN_FLAG);
    if (!fp) {
        NETSTACK_LOGE("TlsCertificate::CertificateFromDer: Couldn't open %{public}s file for reading", data.c_str());
        return false;
    }
    unsigned char cert[FILE_READ_CERT_LEN] = {};
    long certLen = fread(cert, 1, FILE_READ_CERT_LEN, fp);
    fclose(fp);
    const auto *cert_data = reinterpret_cast<const unsigned char*>(cert);
    X509 *x509 = d2i_X509(nullptr, &cert_data, certLen);
    if (!x509) {
        NETSTACK_LOGE("TlsCertificate::CertificateFromDer: x509 is null");
        return false;
    }
    x509_ = X509_dup(x509);
    if (!AnalysisCertificate(certType, x509)) {
        NETSTACK_LOGE("TlsCertificate::CertificateFromDer: AnalysisCertificate is false");
        X509_free(x509);
        return false;
    }
    X509_free(x509);
    return true;
}

bool TLSCertificate::AnalysisCertificate(CertType certType, X509 *x509)
{
    if (certType == CA_CERT) {
        if (!CaCertToString(x509)) {
            NETSTACK_LOGE("TlsCertificate::AnalysisCertificate: CaCertToString is false");
            return false;
        }
    }
    if (certType == LOCAL_CERT) {
        if (!LocalCertToString(x509)) {
            NETSTACK_LOGE("TlsCertificate::AnalysisCertificate: LocalCertToString is false");
            return false;
        }
    }
    if (!SetX509Version(x509)) {
        NETSTACK_LOGE("TlsCertificate::AnalysisCertificate: SetX509Version is false");
        return false;
    }
    if (!SetSerialNumber(x509)) {
        NETSTACK_LOGE("TlsCertificate::AnalysisCertificate: SetSerialNumber is false");
        return false;
    }
    if (!SetNotValidTime(x509)) {
        NETSTACK_LOGE("TlsCertificate::AnalysisCertificate: SetNotValidTime is false");
        return false;
    }
    if (!SetSignatureAlgorithm(x509)) {
        NETSTACK_LOGE("TlsCertificate::AnalysisCertificate: SetSignatureAlgorithm is false");
        return false;
    }
    return true;
}

bool TLSCertificate::CaCertToString(X509 *x509)
{
    if (!x509) {
        NETSTACK_LOGE("TlsCertificate::CaCertToString: x509 is null");
        return false;
    }
    BIO *bio = BIO_new(BIO_s_mem());
    X509_print(bio, x509);
    char data[FILE_READ_CERT_LEN] = {};
    if (!BIO_read(bio, data, FILE_READ_CERT_LEN)) {
        NETSTACK_LOGI("CaCertToString is BIO_read false");
    }
    caCertString_ = std::string(data);
    BIO_free(bio);
    return true;
}

bool TLSCertificate::LocalCertToString(X509 *x509)
{
    if (!x509) {
        NETSTACK_LOGE("TlsCertificate::LocalCertToString: x509 is null");
        return false;
    }
    BIO *bio = BIO_new(BIO_s_mem());
    X509_print(bio, x509);
    char data[FILE_READ_CERT_LEN] = {};
    if (!BIO_read(bio, data, FILE_READ_CERT_LEN)) {
        NETSTACK_LOGE("LocalCertToString BIO_read is false");
    }
    localCertString_ = std::string(data);
    NETSTACK_LOGI("TlsCertificate::LocalCertToString cert is %{public}s", localCertString_.c_str());
    BIO_free(bio);
    return true;
}

bool TLSCertificate::SetX509Version(X509 *x509)
{
    if (!x509) {
        NETSTACK_LOGE("TlsCertificate::SetX509Version: x509 is null");
        return false;
    }
    auto ver = X509_get_version(x509) + 1;
    version_ = CERT_VERSION_FLAG + std::to_string(ver);
    NETSTACK_LOGI("tls version_ %{public}s", version_.c_str());
    return true;
}

bool TLSCertificate::SetSerialNumber(X509 *x509)
{
    if (!x509) {
        NETSTACK_LOGE("TlsCertificate::SetSerialNumber: x509 is null");
        return false;
    }
    ASN1_INTEGER *serial = X509_get_serialNumber(x509);
    if (!serial->length) {
        NETSTACK_LOGE("X509_get_serialNumber length error");
        return false;
    }
	BIGNUM *bn = ASN1_INTEGER_to_BN(serial, nullptr);
    if (!bn) {
	    NETSTACK_LOGE("TlsCertificate::SetSerialNumber: unable to convert ASN1INTEGER to BN");
	    return false;
    }
	serialNumber_ = BN_bn2hex(bn);
	BN_free(bn);
	return true;
}

bool TLSCertificate::SetNotValidTime(X509 *x509)
{
    if (!x509) {
        NETSTACK_LOGE("TlsCertificate::SetNotValidTime: x509 is null");
        return false;
    }
    ASN1_TIME *before = X509_get_notBefore(x509);
    if (!before) {
        NETSTACK_LOGE("TlsCertificate::SetNotValidTime: before is null");
        return false;
    }
    tm tmBefore = {0};
    if (!ASN1_TIME_to_tm(before, &tmBefore)) {
        NETSTACK_LOGE("TlsCertificate::SetNotValidTime: ASN1_TIME_to_tm before is false");
        return false;
    }
    notValidBefore_ = asctime(&tmBefore);

    ASN1_TIME *after = X509_get_notAfter(x509);
    if (!after) {
        NETSTACK_LOGE("TlsCertificate::SetNotValidTime: after is null");
        return false;
    }
    tm tmAfter;
    if (!ASN1_TIME_to_tm(after, &tmAfter)) {
        NETSTACK_LOGE("TlsCertificate::SetNotValidTime: ASN1_TIME_to_tm before is false");
        return false;
    }
    notValidAfter_ = asctime(&tmAfter);
    return true;
}

bool TLSCertificate::SetSignatureAlgorithm(X509 *x509)
{
    int signNid = X509_get_signature_nid(x509);
    const char* sign = OBJ_nid2sn(signNid);
    signatureAlgorithm_ = sign;
    NETSTACK_LOGD("TlsCertificate::SetSignatureAlgorithm OBJ_nid2sn = %{public}s", signatureAlgorithm_.c_str());
    return true;
}

std::string TLSCertificate::GetSignatureAlgorithm() const
{
    return signatureAlgorithm_;
}
std::string TLSCertificate::GetLocalCertString() const
{
    NETSTACK_LOGD("TlsCertificate::GetLocalCertString cert is %{public}s", localCertString_.c_str());
    return localCertString_;
}

Handle TLSCertificate::handle() const
{
    return Handle(x509_);
}
} } // namespace OHOS::NetStack
